//
//  serial.m
//  serialTest
//
//  Created by mt-007 on 2021/3/19.
//

#import "serial.h"

@implementation serial

/**
 * @brief
 *      open a serial port device.
 *
 * @param device device name of the serial port.
 *
 * @return An int indicating the opened file number pointing to the target serial port.
 *      If failed it will return -1.
 */
- (int)serialOpen:(NSString *)device{
    int fd = -1;

    // Open the target device
    fd = open([device UTF8String], O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0) {
        NSLog(@"serialOpen(const char*): Failed to open device %@.", device);
      return (-1);
    }

    // Lock the device access
    if (fcntl(fd, F_SETFL, 0) < 0) {
      NSLog(@"serialOpen(const char*): Failed to lock the access of device %@.", device);
      return (-1);
    }
    
    _isOpen = TRUE;
    comNameFromDEV = device;
    //[serial serialConfig:fd :9600 :8 :'N' :1];
    return fd;
}


/**
 * @return
 *      if success return TRUE, not return FALSE.
 * @brief
 *      check file has been open and serial has been setting.
 */
- (BOOL)checkSerialStatus{
    if(_isOpen && _isConfig){
        // everything is already.
        return TRUE;
    }
    return FALSE;
}


/**
 * @return return the serial setting
 *
 * @brief return the serial infomation like baud rate, and date bit and more...
 */
- (NSDictionary *)returnSerialStatus{
    NSMutableDictionary *serialStatus = [NSMutableDictionary dictionary];
    [serialStatus setValue:[NSString stringWithFormat:@"%d", _baudRate] forKey:@"baudRate"];
    [serialStatus setValue:[NSString stringWithFormat:@"%d", _dateBit] forKey:@"dateBit"];
    [serialStatus setValue:[NSString stringWithFormat:@"%c", _checkParity] forKey:@"checkParity"];
    [serialStatus setValue:[NSString stringWithFormat:@"%d", _stopBit] forKey:@"stopBit"];
    
    return serialStatus;
}


/**
 * @brief Close the serial port and return the result, and make sure self _isConfig and
 * _isOpen be FALSE.
 */
- (int)serialClose:(int)fd {
    _isConfig = FALSE;
    _isOpen = FALSE;
    return close(fd);
}


/**
 * @param fd Opened file number pointing to the serial port.
 * @param baudRate Target baud rate of the serial port. limit choose {2400, 4800, 9600, 19200, 38400, 57600, 115200} .
 * @param dataBit Target data bits length of serial port. limit choose {7, 8}.
 * @param checkParity Target parity of the parity check. limit choose {'N', 'O', 'E'}.
 * @param stopBit Target stop bit of the serial portl. limit choose {1, 2}.
 *
 * @return An int indicating the configuration result.
 *
 * @brief configurate and lock serial port device, it must before the open.
 */
- (int)serialConfig:(int)fd
        andBaudRate:(int)baudRate
         andDataBit:(int)dataBit
     andCheckParity:(char)checkParity
         andStopBit:(int)stopBit {
    struct termios originConfig;
    struct termios targetConfig;
    
    // Read the original terminal configurations and check if failed
    // "0" indicates sucess
    if (tcgetattr(fd, &originConfig) != 0) {
      NSLog(@"serialConfig(int, int, int, char, int): Failed to read terminal description.");
      return (-1);
    }

    // Initialize cflag
    bzero(&targetConfig, sizeof(targetConfig));
    targetConfig.c_cflag |= CLOCAL | CREAD;

    // Set target baud rate
    switch (baudRate) {
      // Baud rate 2400
      case 2400:
        cfsetispeed(&targetConfig, B2400);
        cfsetospeed(&targetConfig, B2400);
        break;

      // Baud rate 4800
      case 4800:
        cfsetispeed(&targetConfig, B4800);
        cfsetospeed(&targetConfig, B4800);
        break;

      // Baud rate 9600
      case 9600:
        cfsetispeed(&targetConfig, B9600);
        cfsetospeed(&targetConfig, B9600);
        break;

      // Baud rate 19200
      case 19200:
        cfsetispeed(&targetConfig, B19200);
        cfsetospeed(&targetConfig, B19200);
        break;

      // Baud rate 38400
      case 38400:
        cfsetispeed(&targetConfig, B38400);
        cfsetospeed(&targetConfig, B38400);
        break;

      // Baud rate 57600
      case 57600:
        cfsetispeed(&targetConfig, B57600);
        cfsetospeed(&targetConfig, B57600);
        break;

      // Baud rate 115200
      case 115200:
        cfsetispeed(&targetConfig, B115200);
        cfsetospeed(&targetConfig, B115200);
        break;
    
    // Baud rate 230400
      case 230400:
        cfsetispeed(&targetConfig, B230400);
        cfsetospeed(&targetConfig, B230400);
        break;

      // Invalid baud rate
      default:
        // Baud rate does not exist
        NSLog(@"serialConfig(int, int, int, char, int): Baud rate %d does not exist.", baudRate);
        return (-1);
    }

    // Set target data size
    targetConfig.c_cflag &= (~CSIZE);
    switch (dataBit) {
      // Data size 7
      case 7:
        targetConfig.c_cflag |= CS7;
        break;

      // Data size 8
      case 8:
        targetConfig.c_cflag |= CS8;
        break;

      // Invalid data size
      default:
        NSLog(@"serialConfig(int, int, int, char, int): Data size %d does not exist.", dataBit);
        return (-1);
    }

    // Set target check parity
    switch (checkParity) {
      // None
      case'N':
        targetConfig.c_cflag &= (~PARENB);
        break;

      // Odd
      case'O':
        targetConfig.c_cflag |= PARENB;
        targetConfig.c_cflag |= PARODD;
        targetConfig.c_iflag |= (INPCK | ISTRIP);
        break;

      // Even
      case'E':
        targetConfig.c_cflag |= PARENB;
        targetConfig.c_cflag &= (~PARODD);
        targetConfig.c_iflag |= (INPCK | ISTRIP);
        break;

      // Invalid check parity
      default:
        NSLog(@"serialConfig(int, int, int, char, int): Check parity %c does not exist.", checkParity);
        return (-1);
    }

    // Set target stop bit
    switch (stopBit) {
      // 1 stop bit
      case 1:
        targetConfig.c_cflag &= (~CSTOPB);
        break;

      // 2 stop bits
      case 2:
        targetConfig.c_cflag |= CSTOPB;
        break;

      // Invalid stop bit
      default:
        NSLog(@"serialConfig(int, int, int, char, int): Stop bit %d does not exist.", stopBit);
        return (-1);
    }

    // Set waiting time
    targetConfig.c_cc[VTIME] = 0;

    // Set minimum byte
    targetConfig.c_cc[VMIN] = 0;

    // Flush input and output queue
    tcflush(fd, TCIFLUSH);

    // Active the target configurations immediately and check the result
    if (tcsetattr(fd, TCSANOW, &targetConfig) != 0) {
        // "0" indicates success
      NSLog(@"serialConfig(int, int, int, char, int): Failed to configurate terminal description.");
      return (-1);
    }

    // Return success
    _isConfig = TRUE;
    return 1;
}


/**
 * @param fd Opened file number pointing to the serial port.
 * @param buf Pointer to the target buffer to put read bytes.
 * @param len Length of bytes to read.
 * @return An int indicating the length of bytes read.
 *      but return type is not int, is ssize_t == unsigned int. so zero will be return if failed read.
 *
 * @brief Read bytes from a serial port device.
 */
- (ssize_t)serialRead:(int)fd andBuff:(void*)buf andBuffLength:(size_t)len {
    // Check the parameters
    if(!len) {
      NSLog(@"serialRead(int, void*, int): The length of bytes to read must be positive.");
      return (-1);
    }

    // Read bytes and return the result
    return read(fd, buf, len);
}


/**
 * @param fd Opened file number pointing to the serial port.
 * @param buf Pointer to the target buffer to put write bytes.
 * @param len Length of bytes to write.
 * @return An int indicating the length of bytes written. If failed return zero.
 * @brief Write bytes to a serial port device.
 */
- (ssize_t)serialWrite:(int)fd andBuff:(void*)buf andBuffLength:(size_t)len {
    // Check the parameters
    if(!len) {
      printf("serialWrite(int, void*, int): The length of buffer must be positive.");
      return (-1);
    }

    // Write bytes and return the result
    return write(fd, buf, len);
}

@end

